home *** CD-ROM | disk | FTP | other *** search
/ GameStar 2004 April / Gamestar_61_2004-04_dvdb.iso / DVDStar / Editace / hltp.exe / {app} / Applications / QuArK / plugins / mapbotwaypointer.py < prev    next >
Text File  |  2004-01-05  |  35KB  |  875 lines

  1. # QuArK  -  Quake Army Knife
  2. #
  3. # Copyright (C) 1996-2000 Armin Rigo
  4. # THIS FILE IS PROTECTED BY THE GNU GENERAL PUBLIC LICENCE
  5. # FOUND IN FILE "COPYING.TXT"
  6. #
  7. #$Header: /cvsroot/quark/runtime/plugins/mapbotwaypointer.py,v 1.3 2003/12/18 21:51:46 peter-b Exp $
  8.  
  9. ## Fredrick_vamstad@hotmail.com
  10.  
  11. Info = {
  12.    "plug-in":       "Bot Waypointer",
  13.    "desc":          "Bot Waypointer",
  14.    "date":          "? may 2002",
  15.    "author":        "Decker",
  16.    "author e-mail": "decker@planetquake.com",
  17.    "quark":         "Version 6.x"
  18. }
  19.  
  20. import sys
  21. import struct
  22. import quarkx
  23. import quarkpy.qmacro
  24. import quarkpy.mapmenus
  25. from quarkpy.qeditor import *
  26. from quarkpy.mapduplicator import *
  27. from quarkpy.qhandles import *
  28.  
  29. #
  30. # Base class for load/save bot-waypoint files
  31. #
  32. class BotWaypointConverter:
  33.     m_waypoints = []
  34.  
  35.     def __init__(self):
  36.         pass
  37.  
  38.     def Load(self, filename):
  39.         raise
  40.  
  41.     def Save(self, filename):
  42.         raise
  43.  
  44. #
  45. # Class to load/save HPBBot v2.1 waypoint files
  46. #
  47. class BotWaypointConvertHPB(BotWaypointConverter):
  48.     "HPB-Bot v2.1 .WPT file load/save"
  49.  
  50.     m_HPB_flags = \
  51.         [(      1 ,"team_bit1"        ,"allow for 4 teams (0-3)"                                   ) \
  52.         ,(      2 ,"team_bit2"        ,"allow for 4 teams (0-3)"                                   ) \
  53.         ,(      4 ,"teamspecific"     ,"waypoint only for specified team"                          ) \
  54.         ,(      8 ,"crouch"           ,"must crouch to reach this waypoint"                        ) \
  55.         ,(     16 ,"ladder"           ,"waypoint on a ladder"                                      ) \
  56.         ,(     32 ,"lift"             ,"wait for lift to be down before approaching this waypoint" ) \
  57.         ,(     64 ,"door"             ,"wait for door to open"                                     ) \
  58.         ,(    128 ,"health"           ,"health kit (or wall mounted) location"                     ) \
  59.         ,(    256 ,"armor"            ,"armor (or HEV) location"                                   ) \
  60.         ,(    512 ,"ammo"             ,"ammo location"                                             ) \
  61.         ,(   1024 ,"sniper"           ,"sniper waypoint (a good sniper spot)"                      ) \
  62.         ,(   2048 ,"flag"             ,"flag position (or hostage or president)"                   ) \
  63.         ,(   2048 ,"flf_cap"          ,"Front Line Force capture point"                            ) \
  64.         ,(   4096 ,"flag_goal"        ,"flag return position (or rescue zone)"                     ) \
  65.         ,(   4096 ,"flf_defend"       ,"Front Line Force defend point"                             ) \
  66.         ,(   8192 ,"prone"            ,"go prone (laying down)"                                    ) \
  67.         ,(  16384 ,"aiming"           ,"aiming waypoint"                                           ) \
  68.         ,(  32768 ,"sentrygun"        ,"sentry gun waypoint for TFC"                               ) \
  69.         ,(  65536 ,"dispenser"        ,"dispenser waypoint for TFC"                                ) \
  70.         ,( 131072 ,"weapon"           ,"weapon_ entity location"                                   ) \
  71.         ,( 262144 ,"jump"             ,"jump waypoint"                                             ) \
  72.         ]
  73.  
  74.     def FlagsToSpecs(self, flags, obj):
  75.         for flag, spec, hint in self.m_HPB_flags:
  76.             if (flag & flags) == flag:
  77.                 obj["_"+spec] = "1"
  78.             else:
  79.                 obj["_"+spec] = "0"
  80.  
  81.     def SpecsToFlags(self, obj):
  82.         flags = 0
  83.         for flag, spec, hint in self.m_HPB_flags:
  84.             try:
  85.                 flags = flags + (flag * int(obj["_"+spec] == "1"))
  86.             except:
  87.                 pass
  88.         return flags
  89.  
  90.     def Load(self, filename):
  91.         self.m_waypoints = []
  92.  
  93.         f = open(filename, "rb")
  94.  
  95.         # Info on the 'struct' object: http://www.python.org/doc/current/lib/module-struct.html
  96.  
  97.         # Read the header
  98.         filetype = f.read(8)
  99.         waypoint_file_version, waypoint_file_flags, number_of_waypoints = struct.unpack("iii", f.read(4*3))
  100.         mapname = f.read(32)
  101.  
  102.         # Check the header
  103.         if (filetype != "HPB_bot\x00"):
  104.             raise "Fail in file-header: Not 'HPB_bot', but '" + filetype + "'"
  105.         if (waypoint_file_version != 4):
  106.             raise "Fail in file-header: Not version '4', but '" + str(waypoint_file_version) +"'"
  107.  
  108.         # Read waypoint-coordinates
  109.         for wp in range(number_of_waypoints):
  110.             flags, vecx, vecy, vecz = struct.unpack("ifff", f.read(4 + (4*3)))
  111.  
  112.             obj = quarkx.newobj("dup botwaypointerpoint:d")
  113.             obj["macro"] = "dup botwaypointerpoint"
  114.             obj["origin"] = str(vecx)+" "+str(vecy)+" "+str(vecz)
  115.             obj.translate(quarkx.vect(0, 0, 0))
  116.             obj["targetname"] = "wp"+str(wp)
  117.             self.FlagsToSpecs(flags, obj)
  118.  
  119.             self.m_waypoints.append(obj)
  120.  
  121.         # Read waypoint-paths
  122.         for wp in range(number_of_waypoints):
  123.             obj = self.m_waypoints[wp]
  124.             num, = struct.unpack("H", f.read(2))
  125.             for p in range(num):
  126.                 wp_idx, = struct.unpack("H", f.read(2))
  127.                 obj["target"+str(p)] = "wp"+str(wp_idx)
  128.  
  129.         f.close()
  130.  
  131.     def Save(self, filename):
  132.         f = open(filename, "wb")
  133.  
  134.         # Write the header
  135.         f.write("HPB_bot\x00")
  136.         f.write(struct.pack("iii", 4, 0, len(self.m_waypoints)))
  137.         data = filename.split("\\")[-1]
  138.         data = data.split(".")[0]
  139.         data = data + "\x00"*(31-len(data))+"\x00"
  140.         f.write(data)
  141.  
  142.         # Write waypoint-coordinates
  143.         for wp in range(len(self.m_waypoints)):
  144.             obj = self.m_waypoints[wp]
  145.  
  146.             flags = self.SpecsToFlags(obj)
  147.             f.write(struct.pack("i", flags))
  148.  
  149.             vecx, vecy, vecz = quarkx.vect(obj["origin"]).tuple
  150.             f.write(struct.pack("fff", vecx, vecy, vecz))
  151.  
  152.         # Write waypoint-paths
  153.         for wp in range(len(self.m_waypoints)):
  154.             obj = self.m_waypoints[wp]
  155.             p = 0
  156.             wp_idxs = ""
  157.             for i in range(16):
  158.                 arg = obj["target"+str(i)]
  159.                 if ((arg is not None) and (arg != "")):
  160.                     for o in range(len(self.m_waypoints)):
  161.                         if (self.m_waypoints[o]["targetname"] == arg):
  162.                             wp_idxs = wp_idxs + struct.pack("H", o)
  163.                             p = p + 1
  164.                             break
  165.             f.write(struct.pack("H", p))
  166.             f.write(wp_idxs)
  167.  
  168.         f.close()
  169.  
  170. #
  171. # Class to load/save ACEBot/LTKBot waypoint files
  172. #
  173. class BotWaypointConvertACE(BotWaypointConverter):
  174.     "ACEBot .LTK file load/save"
  175.  
  176.     m_ACE_types = \
  177.         [(0 ,"MOVE"         ,"" ) \
  178.         ,(1 ,"LADDER"       ,"" ) \
  179.         ,(2 ,"PLATFORM"     ,"" ) \
  180.         ,(3 ,"TELEPORTER"   ,"" ) \
  181.         ,(4 ,"ITEM"         ,"" ) \
  182.         ,(5 ,"WATER"        ,"" ) \
  183.         ,(6 ,"GRAPPLE"      ,"" ) \
  184.         ,(7 ,"JUMP"         ,"" ) \
  185.         ,(8 ,"DOOR"         ,"" ) \
  186.         ]
  187.  
  188.     def TypeToSpec(self, master_type, obj):
  189.         for type, spec, hint in self.m_ACE_types:
  190.             if (type == master_type):
  191.                 obj["type"] = spec
  192.                 break
  193.  
  194.     def SpecToType(self, obj):
  195.         for type, spec, hint in self.m_ACE_types:
  196.             if (obj["type"] == spec):
  197.                 return type
  198.         raise "ACEBot: Unknown spec-for-type: '"+obj["type"]+"'"
  199.  
  200.     def Load(self, filename):
  201.         self.m_waypoints = []
  202.  
  203.         f = open(filename, "rb")
  204.  
  205.         # Read the header
  206.         version, = struct.unpack("i", f.read(4)) # int
  207.  
  208.         # Check the header
  209.         if (version != 4):
  210.             raise "Fail in file-header: Not version '4', but '" + str(version) + "'"
  211.  
  212.         num_of_nodes, = struct.unpack("i", f.read(4))    # int
  213.         num_of_items, = struct.unpack("i", f.read(4))    # int
  214.  
  215.         # setup a progress-indicator
  216.         progressbar = quarkx.progressbar(509, (num_of_nodes * 2) + num_of_items)
  217.         try:
  218.  
  219.             # read nodes
  220.             for i in range(num_of_nodes):
  221.                 progressbar.progress()
  222.                 vecx, vecy, vecz = struct.unpack("fff", f.read(4*3))    # vec3_t
  223.                 node_type, = struct.unpack("i", f.read(4))              # int
  224.                 node_num, dummy = struct.unpack("hh", f.read(2+2))      # short int (+ 'short int' padded)
  225.  
  226.                 obj = quarkx.newobj("dup botwaypointerpoint:d")
  227.                 obj["macro"] = "dup botwaypointerpoint"
  228.                 obj["origin"] = str(vecx)+" "+str(vecy)+" "+str(vecz)
  229.                 obj.translate(quarkx.vect(0, 0, 0))
  230.                 obj["targetname"] = "wp"+str(node_num)
  231.                 self.TypeToSpec(node_type, obj)
  232.  
  233.                 for j in range(12):     # 12 = MAXLINKS
  234.                     target_node, dummy, cost = struct.unpack("hhf", f.read(2+2+4)) # short int (+ 'short int' padded) float
  235.  
  236.                     if (target_node > 0):
  237.                         obj["target"+str(j)] = "wp"+str(target_node)
  238.                         obj["target"+str(j)+"_cost"] = str(int(cost))
  239.  
  240.                 self.m_waypoints.append(obj)
  241.  
  242.             # read path_table, and compress it into minimal number of specs.
  243.             for i in range(num_of_nodes):
  244.                 progressbar.progress()
  245.                 obj = self.m_waypoints[i]
  246.                 for j in range(num_of_nodes):
  247.                     path_to_node, = struct.unpack("h", f.read(2)) # short int
  248.                     if (path_to_node >= 0):
  249.                         try:
  250.                             value = obj["via_wp"+str(path_to_node)+"_to"] + ";" + "wp"+str(j)
  251.                         except:
  252.                             value = "wp"+str(j)
  253.                         obj["via_wp"+str(path_to_node)+"_to"] = value
  254.  
  255.             # read items
  256.             for i in range(num_of_items):
  257.                 progressbar.progress()
  258.                 item, weight, ent_ptr, node = struct.unpack("ifii", f.read(4 + 4 + 4 + 4))
  259.  
  260.         finally:
  261.             progressbar.close()
  262.  
  263.         f.close()
  264.  
  265.     def Save(self, filename):
  266.         f = open(filename, "wb")
  267.  
  268.         # Write the header
  269.         f.write(struct.pack("i", 4)) # int - version 4
  270.  
  271.         num_of_nodes = len(self.m_waypoints)
  272.         num_of_items = 0
  273.  
  274.         data = struct.pack("i", num_of_nodes) + struct.pack("i", num_of_items) # int int
  275.         f.write(data)
  276.  
  277.         # Build translation-table for 'targetnames_to_nodenum'
  278.         targetnames_to_nodenum = {}
  279.         for i in range(num_of_nodes):
  280.             obj = self.m_waypoints[i]
  281.             targetnames_to_nodenum[obj["targetname"]] = i
  282.  
  283.         # setup a progress-indicator
  284.         progressbar = quarkx.progressbar(5450, (num_of_nodes * 2) + num_of_items)
  285.         try:
  286.  
  287.             # Write nodes
  288.             for i in range(num_of_nodes):
  289.                 progressbar.progress()
  290.                 obj = self.m_waypoints[i]
  291.  
  292.                 try:
  293.                     vecx, vecy, vecz = quarkx.vect(obj["origin"]).tuple
  294.                     f.write(struct.pack("fff", vecx, vecy, vecz))
  295.                 except:
  296.                     raise "Error writing origin for "+obj["targetname"]
  297.  
  298.                 try:
  299.                     f.write(struct.pack("i", self.SpecToType(obj)))
  300.                 except:
  301.                     raise "Error writing type for "+obj["targetname"]
  302.  
  303.                 try:
  304.                     node_num = targetnames_to_nodenum[obj["targetname"]]
  305.                     dummy = 0
  306.                     f.write(struct.pack("hh", node_num, dummy))
  307.                 except:
  308.                     raise "Error writing node_num for "+obj["targetname"]
  309.  
  310.                 cnt = 12
  311.                 for j in range(12):
  312.                     target_node = obj["target"+str(j)]
  313.                     cost = obj["target"+str(j)+"_cost"]
  314.                     if ((target_node is not None) and (target_node != "")):
  315.                         try:
  316.                             node_num = targetnames_to_nodenum[target_node]
  317.                             dummy = 0
  318.                             f.write(struct.pack("hhf", node_num, dummy, float(cost))) # short int (+ 'short int' padded) float
  319.                             cnt = cnt - 1
  320.                         except:
  321.                             raise "Error writing nodelist #"+str(j)+" for "+obj["targetname"]
  322.                 for j in range(cnt):
  323.                     f.write(struct.pack("hhf", -1, 0, 0))
  324.  
  325.             # Write path_table. Uncompress it from its minimal 'structure'
  326.             for i in range(num_of_nodes):
  327.                 progressbar.progress()
  328.                 obj = self.m_waypoints[i]
  329.  
  330.                 node_path = []
  331.                 for j in range(num_of_nodes):
  332.                     node_path = node_path + [int(-1)]
  333.  
  334.                 for spec in obj.dictspec.keys():
  335.                     if (spec[:4] == "via_"):
  336.                         wp_num = spec.split("_")[1]
  337.                         via_node_num = targetnames_to_nodenum[wp_num]
  338.                         arg = obj[spec]
  339.                         for wp_num in arg.split(";"):
  340.                             node_path[targetnames_to_nodenum[wp_num]] = via_node_num
  341.  
  342.                 for j in range(num_of_nodes):
  343.                     f.write(struct.pack("h", node_path[j]))
  344.  
  345.             # Write items
  346.             for i in range(num_of_items):
  347.                 progressbar.progress()
  348.  
  349.         finally:
  350.             progressbar.close()
  351.  
  352.         f.close()
  353.  
  354. #
  355. # Supported file-extension types
  356. #
  357. gBotFileExtFilter = ["Supported bot-types|*.wpt;*.ltk", "HPBBot (*.wpt)|*.wpt", "ACEBot/LTKBot (*.ltk)|*.ltk"]
  358.  
  359. #
  360. # Load macro
  361. #
  362. def macro_botwaypointer_loadfile(self):
  363.     editor = mapeditor()
  364.     if editor is None:
  365.         return
  366.     dup = editor.layout.explorer.uniquesel
  367.     if dup is None:
  368.         return
  369.  
  370.     files = quarkx.filedialogbox("Load bot waypoint file...", "", gBotFileExtFilter, 0, "*.*")
  371.     if len(files) == 1:
  372.         files[0] = files[0].lower()
  373.         if (files[0][-3:] == "wpt"):
  374.             aObj = BotWaypointConvertHPB()
  375.         elif (files[0][-3:] == "ltk"):
  376.             aObj = BotWaypointConvertACE()
  377.         else:
  378.             raise "File-extension not supported '"+files[0][-3:]+"'."
  379.  
  380.         aObj.Load(files[0])
  381.  
  382.         # Setup undo/redo possibility
  383.         undo = quarkx.action()
  384.  
  385.         undo.setspec(dup, "last_file", files[0])
  386.  
  387.         # Remove old waypoints (everything below the selected duplicator-object)
  388.         for d in dup.subitems:
  389.             undo.exchange(d, None)
  390.  
  391.         # Add the new waypoints
  392.         for w in aObj.m_waypoints:
  393.             undo.put(dup, w)
  394.  
  395.         # Do the undo/redo stuff, and redraw the views.
  396.         editor.ok(undo, 'Load bot waypoint file')
  397.  
  398.         editor.layout.explorer.sellist = [dup]
  399.         editor.invalidateviews()
  400.  
  401. quarkpy.qmacro.MACRO_botwaypointer_loadfile = macro_botwaypointer_loadfile
  402.  
  403. #
  404. # Save macro
  405. #
  406. def macro_botwaypointer_savefile(self):
  407.     editor = mapeditor()
  408.     if editor is None:
  409.         return
  410.     dup = editor.layout.explorer.uniquesel
  411.     if dup is None:
  412.         return
  413.  
  414.     last_file = dup["last_file"]
  415.  
  416.     files = quarkx.filedialogbox("Save bot waypoint file...", "", gBotFileExtFilter, 1, last_file)
  417.     if len(files) == 1:
  418.         files[0] = files[0].lower()
  419.         if (files[0][-3:] == "wpt"):
  420.             aObj = BotWaypointConvertHPB()
  421.         elif (files[0][-3:] == "ltk"):
  422.             aObj = BotWaypointConvertACE()
  423.         else:
  424.             raise "File-extension not supported '"+files[0][-3:]+"'."
  425.  
  426.         dup["last_file"] = files[0]
  427.  
  428.         # Store the waypoints
  429.         for w in dup.subitems:
  430.             aObj.m_waypoints.append(w)
  431.  
  432.         aObj.Save(files[0])
  433.  
  434. quarkpy.qmacro.MACRO_botwaypointer_savefile = macro_botwaypointer_savefile
  435.  
  436.  
  437. #
  438. #
  439. #
  440. colors = [0xB00000,
  441.           0x00B000,
  442.           0x0000B0,
  443.           0xB0B000,
  444.           0x00B0B0,
  445.           0xB000B0,
  446.           0xB0B0B0]
  447.  
  448. def FindEntityByTargetname(name, list):
  449.     for e in list:
  450.         if (e["targetname"] == name):
  451.             return e
  452.     return None
  453.  
  454. def ShortestRouteTree(view, cv, obj, waypoints, cnt=0):
  455.     if (cnt >= 90):
  456.         # a simple test to ensure we don't get into an endless loop
  457.         print "Possible cyclic path:", cnt, obj["targetname"], waypoints
  458.         if (cnt >= 100):
  459.             # stop traversing
  460.             return 1
  461.  
  462.     pp2 = view.proj(obj.origin)
  463.     for spec in obj.dictspec.keys():
  464.         if (spec[0:4] == "via_"):
  465.             viaobj = FindEntityByTargetname(spec.split("_")[1], obj.treeparent.subitems)
  466.             if (viaobj is not None):
  467.                 routes = obj[spec].split(";")
  468.  
  469.                 # Use only those waypoint-names, which exists in both lists ('routes' and 'waypoints').
  470.                 def isinlist(wp, routes=routes):
  471.                     if wp in routes:
  472.                         return 1
  473.                     return 0
  474.                 theseroutes = filter(isinlist, waypoints)
  475.  
  476.                 # Remove from the 'waypoints' list, those waypoint-names who also exists in the 'theseroutes' list.
  477.                 def isnotinlist(wp, theseroutes=theseroutes):
  478.                     if wp in theseroutes:
  479.                         return 0
  480.                     return 1
  481.                 newwaypoints = filter(isnotinlist, waypoints)
  482.                 waypoints = newwaypoints
  483.  
  484.                 # If there are any waypoint-names in 'theseroutes' list, traverse them too.
  485.                 if (len(theseroutes) > 0):
  486.                     cv.line(view.proj(viaobj.origin), pp2)
  487.                     if (ShortestRouteTree(view, cv, viaobj, theseroutes, cnt+1) != 0):
  488.                         return 1
  489.     return 0
  490.  
  491. #
  492. #
  493. #
  494. def FindSelectedOne(list):
  495.     for e in list:
  496.         if (e.selected):
  497.             return e
  498.     return None
  499.  
  500. #
  501. #
  502. #
  503. class BotWaypointerPointHandle(CenterHandle):
  504.  
  505.     def __init__(self, points_to_list, routes_to_list, pos, centerof, color=RED, caninvert=0):
  506.         CenterHandle.__init__(self, pos, centerof, color, caninvert)
  507.         self.points_to_list = points_to_list
  508.         self.routes_to_list = routes_to_list
  509.         self.hint = "'"+centerof["targetname"]+"' = targetname.||The black thin arrows, shows which other waypoints this one directly points to, by using its 'target##' specifics.\n\nThe thick colored lines illustrates its nearest neighbours, which are used for the shortest-path-matrix, by using its 'via_wp###_to' specifics."
  510.  
  511.     def draw(self, view, cv, draghandle=None):
  512.         myparent = self.centerof.treeparent
  513.         myself = self.centerof
  514.         if (myself.selected):
  515.             myparent["lastwaypointorigin"] = myself["origin"]
  516.         if (myparent["shortestpathdisplay"] == "1"):
  517.             if (myself.selected):
  518.                 nextclr = 0
  519.                 for spec in myself.dictspec.keys():
  520.                     if (spec[0:4] == "via_"):
  521.                         cv.pencolor = colors[nextclr]
  522.                         nextclr = (nextclr + 1) % 6
  523.                         viaobj = FindEntityByTargetname(spec.split("_")[1], myparent.subitems)
  524.                         if (viaobj is not None):
  525.                             pp2 = view.proj(viaobj.origin)
  526.                             cv.penwidth = 3
  527.                             cv.line(view.proj(self.pos), pp2)
  528.                             cv.penwidth = 1
  529.                             via_routes = myself[spec].split(";")
  530.                             if (ShortestRouteTree(view, cv, viaobj, via_routes) != 0):
  531.                                 raise "Possible cyclic path"
  532.                            #for wp in via_routes:
  533.                            #    obj = FindEntityByTargetname(wp, myparent.subitems)
  534.                            #    if (obj is not None):
  535.                            #        cv.line(view.proj(obj.origin), pp2)
  536.         else:
  537.             if (self.routes_to_list is not None):
  538.                 cv.penwidth = 3
  539.                 nextclr = 0
  540.                 pp2 = view.proj(self.pos)
  541.                 for route_to in self.routes_to_list:
  542.                     cv.pencolor = colors[nextclr]
  543.                     nextclr = (nextclr + 1) % 6
  544.                     cv.line(view.proj(route_to), pp2)
  545.         if (self.points_to_list is not None):
  546.             cv.pencolor = 0
  547.             cv.penwidth = 1
  548.             for point_to in self.points_to_list:
  549.                 Arrow(cv, view, self.pos, point_to)
  550.         CenterHandle.draw(self, view, cv, draghandle)
  551.  
  552.  
  553.     def menu(self, editor, view):
  554.  
  555.         def AddTwoWayClick(m, self=self, editor=editor):
  556.             myparent = self.centerof.treeparent
  557.             myself   = self.centerof
  558.             theSelected = FindSelectedOne(myparent.subitems)
  559.             if (theSelected is not None):
  560.                 undo = quarkx.action()
  561.  
  562.                 try:
  563.                     use_specname = None
  564.                     use_num = 0
  565.                     for spec in theSelected.dictspec.keys():
  566.                         if (spec[:6] == "target" and spec != "targetname"):
  567.                             if (theSelected[spec] == myself["targetname"]):
  568.                                 raise "already exist"
  569.                             if (theSelected[spec] is None or theSelected[spec] == ""):
  570.                                 use_specname = spec
  571.                             use_num = max(use_num, int(spec[6:]))
  572.                     if (use_specname is None):
  573.                         use_specname = "target" + str(use_num + 1)
  574.                     undo.setspec(theSelected, use_specname, myself["targetname"])
  575.                 except:
  576.                     pass
  577.  
  578.                 try:
  579.                     use_specname = None
  580.                     use_num = 0
  581.                     for spec in myself.dictspec.keys():
  582.                         if (spec[:6] == "target" and spec != "targetname"):
  583.                             if (myself[spec] == theSelected["targetname"]):
  584.                                 raise "already exist"
  585.                             if (myself[spec] is None or myself[spec] == ""):
  586.                                 use_specname = spec
  587.                             use_num = max(use_num, int(spec[6:]))
  588.                     if (use_specname is None):
  589.                         use_specname = "target" + str(use_num + 1)
  590.                     undo.setspec(myself, use_specname, theSelected["targetname"])
  591.                 except:
  592.                     pass
  593.  
  594.                 undo.ok(editor.Root, "add two-way target")
  595.                 editor.invalidateviews()
  596.  
  597.         def AddOneWayClick(m, self=self, editor=editor):
  598.             myparent = self.centerof.treeparent
  599.             myself   = self.centerof
  600.             theSelected = FindSelectedOne(myparent.subitems)
  601.             if (theSelected is not None):
  602.                 undo = quarkx.action()
  603.  
  604.                 try:
  605.                     use_specname = None
  606.                     use_num = 0
  607.                     for spec in theSelected.dictspec.keys():
  608.                         if (spec[:6] == "target" and spec != "targetname"):
  609.                             if (theSelected[spec] == myself["targetname"]):
  610.                                 raise "already exist"
  611.                             if (theSelected[spec] is None or theSelected[spec] == ""):
  612.                                 use_specname = spec
  613.                             use_num = max(use_num, int(spec[6:]))
  614.                     if (use_specname is None):
  615.                         use_specname = "target" + str(use_num + 1)
  616.                     undo.setspec(theSelected, use_specname, myself["targetname"])
  617.                 except:
  618.                     pass
  619.  
  620.                 undo.ok(editor.Root, "add two-way target")
  621.                 editor.invalidateviews()
  622.  
  623.         def RemTwoWayClick(m, self=self, editor=editor):
  624.             myparent = self.centerof.treeparent
  625.             myself   = self.centerof
  626.             theSelected = FindSelectedOne(myparent.subitems)
  627.             if (theSelected is not None):
  628.                 undo = quarkx.action()
  629.                 for spec in theSelected.dictspec.keys():
  630.                     if (theSelected[spec] == myself["targetname"]):
  631.                         undo.setspec(theSelected, spec, None)
  632.                 for spec in myself.dictspec.keys():
  633.                     if (myself[spec] == theSelected["targetname"]):
  634.                         undo.setspec(myself, spec, None)
  635.                 undo.ok(editor.Root, "remove two-way target")
  636.                 editor.invalidateviews()
  637.  
  638.         def RemOneWayClick(m, self=self, editor=editor):
  639.             myparent = self.centerof.treeparent
  640.             myself   = self.centerof
  641.             theSelected = FindSelectedOne(myparent.subitems)
  642.             if (theSelected is not None):
  643.                 undo = quarkx.action()
  644.                 for spec in theSelected.dictspec.keys():
  645.                     if (theSelected[spec] == myself["targetname"]):
  646.                         undo.setspec(theSelected, spec, None)
  647.                 undo.ok(editor.Root, "remove one-way target")
  648.                 editor.invalidateviews()
  649.  
  650.         def ShortestPathDisplayClick(m, self=self, editor=editor):
  651.             myparent = self.centerof.treeparent
  652.             shortestpathdisplay = not int(myparent["shortestpathdisplay"])
  653.             m.state = quarkpy.qmenu.checked and shortestpathdisplay
  654.             myparent["shortestpathdisplay"] = str(shortestpathdisplay)
  655.             editor.invalidateviews()
  656.  
  657.         myparent = self.centerof.treeparent
  658.  
  659.         menu_add_twoway = quarkpy.qmenu.item("Add two-way target",    AddTwoWayClick, "|stuff to type here...")
  660.         menu_add_oneway = quarkpy.qmenu.item("Add one-way target",    AddOneWayClick, "|stuff to type here...")
  661.         menu_rem_twoway = quarkpy.qmenu.item("Remove two-way target", RemTwoWayClick, "|stuff to type here...")
  662.         menu_rem_oneway = quarkpy.qmenu.item("Remove one-way target", RemOneWayClick, "|stuff to type here...")
  663.  
  664.         theSelected = FindSelectedOne(myparent.subitems)
  665.  
  666.         # Enable/disable menu-items depending on the state and spec/args of the two in question.
  667.         if (theSelected is None or theSelected == self.centerof):
  668.             menu_add_twoway.state = quarkpy.qmenu.disabled
  669.             menu_add_oneway.state = quarkpy.qmenu.disabled
  670.             menu_rem_twoway.state = quarkpy.qmenu.disabled
  671.             menu_rem_oneway.state = quarkpy.qmenu.disabled
  672.         else:
  673.             # Figure out, who targets who.
  674.             myself_targetname       = self.centerof["targetname"]
  675.             theselected_targetname  = theSelected["targetname"]
  676.             i_target_selected   = 0
  677.             selected_targets_me = 0
  678.             for spec in self.centerof.dictspec.keys():
  679.                 if (spec[:6] == "target" and self.centerof[spec] == theselected_targetname):
  680.                     i_target_selected = 1
  681.                     break
  682.             for spec in theSelected.dictspec.keys():
  683.                 if (spec[:6] == "target" and theSelected[spec] == myself_targetname):
  684.                     selected_targets_me = 1
  685.                     break
  686.             menu_add_twoway.state = not (not i_target_selected or not selected_targets_me) and quarkpy.qmenu.disabled
  687.             menu_add_oneway.state = not (not selected_targets_me)                          and quarkpy.qmenu.disabled
  688.             menu_rem_twoway.state = not (i_target_selected and selected_targets_me)        and quarkpy.qmenu.disabled
  689.             menu_rem_oneway.state = not (selected_targets_me)                              and quarkpy.qmenu.disabled
  690.  
  691.         # Set up 'shortest path display' menuitem-checkbox
  692.         menu_shortestpath = quarkpy.qmenu.item("Shortest-path display", ShortestPathDisplayClick, "|stuff to type here...")
  693.         try:
  694.             shortestpathdisplay = int(myparent["shortestpathdisplay"])
  695.         except:
  696.             shortestpathdisplay = 0
  697.         myparent["shortestpathdisplay"] = str(shortestpathdisplay)
  698.         menu_shortestpath.state = quarkpy.qmenu.checked and shortestpathdisplay
  699.  
  700.         return [menu_add_twoway, menu_add_oneway, menu_rem_twoway, menu_rem_oneway, qmenu.sep, menu_shortestpath]
  701.  
  702. #
  703. #
  704. #
  705. class BotWaypointerPoint(DuplicatorManager):
  706.  
  707.     def buildimages(self, singleimage=None):
  708.         pass
  709.  
  710.     def handles(self, editor, view):
  711.         myparent = self.dup.treeparent
  712.         myself = self.dup
  713.         hndls = []
  714.  
  715.         # Examine the spec/args for directly associated waypoints to myself
  716.         points_to_list = []
  717.         routes_to_list = []
  718.         to_list = []
  719.         for spec in myself.dictspec.keys():
  720.             if (spec[:6] == "target" and myself[spec] is not None and myself[spec] != ""):
  721.                 points_to = FindEntityByTargetname(myself[spec], myparent.subitems)
  722.                 if (points_to is not None):
  723.                     points_to_list.append(points_to.origin)
  724.                     if (points_to not in to_list):
  725.                         to_list.append(points_to)
  726.             if (spec[:4] == "via_" and myself[spec] is not None and myself[spec] != ""):
  727.                 routes_to = FindEntityByTargetname(spec.split("_")[1], myparent.subitems)
  728.                 if (routes_to is not None):
  729.                     routes_to_list.append(routes_to.origin)
  730.                     if (routes_to not in to_list):
  731.                         to_list.append(points_to)
  732.  
  733.         # First add the selected handle, so it will be drawn first (z-order of drawn lines)
  734.         hndls.append(BotWaypointerPointHandle(points_to_list, routes_to_list, self.dup.origin, self.dup))
  735.  
  736.         # Then append those found in the spec/args.
  737.         def createhandle(o):
  738.             return BotWaypointerPointHandle(None, None, o.origin, o)
  739.         hndls = hndls + map(createhandle, to_list)
  740.  
  741.         # Lastly show also the rest within a sphere of 512 units
  742.         for obj in myparent.subitems:
  743.             if (obj in hndls):
  744.                 # Don't take objects with twice!
  745.                 continue
  746.             if abs(obj.origin - self.dup.origin) < 512:
  747.                 # Only those within 512 units
  748.                 hndls.append(BotWaypointerPointHandle(None, None, obj.origin, obj))
  749.  
  750.         # And return the list of handles
  751.         return hndls
  752.  
  753.  
  754. #
  755. #
  756. #
  757. class BotWaypointer(StandardDuplicator):
  758.  
  759.     def buildimages(self, singleimage=None):
  760.         # Don't allow dissociate images to work
  761.         if (singleimage is not None):
  762.             return []
  763.         # Local function to create dummy entity, so the waypoints become visible in the 2D-views
  764.         # without the need to have this duplicator selected.
  765.         def makeentitiy(obj):
  766.             newobj = quarkx.newobj("info_waypoint:e")
  767.             newobj["origin"] = str(obj.origin)
  768.             newobj.translate(quarkx.vect(0, 0, 0)) # damn - if this isn't here, the entities will not show in the views
  769.             return newobj
  770.         # Create the list of dummy entities
  771.         botwaypointerpointentities = map(makeentitiy, self.dup.subitems)
  772.         return botwaypointerpointentities
  773.  
  774.     def handles(self, editor, view):
  775.         def makehandle(obj):
  776.             return quarkpy.qhandles.CenterHandle(obj.origin, obj)
  777.         botwaypointerpointhandles = map(makehandle, self.dup.subitems)
  778.         return botwaypointerpointhandles
  779.  
  780. #
  781. #
  782. #
  783. def NewWaypointTargetname(list):
  784.     MaxNum = 0
  785.     for e in list:
  786.         if (e["targetname"][:2] == "wp"):
  787.             Num = int(e["targetname"][2:])
  788.             if (Num > MaxNum):
  789.                 MaxNum = Num
  790.     MaxNum = MaxNum + 1
  791.     MaxTargetname = "wp" + str(MaxNum)
  792.  
  793.     return MaxTargetname
  794.  
  795. #
  796. #
  797. #
  798. def PasteBotWaypointClick(m):
  799.     editor = mapeditor()
  800.     if (editor is None):
  801.         return
  802.     # Check that the correct object is in the ClipBoard
  803.     clipboard = quarkx.pasteobj(1)
  804.     if ((len(clipboard) != 1) or (clipboard[0]["macro"] != "dup botwaypointerpoint")):
  805.         quarkx.msgbox("Clipboard does not contain exactly one 'dup botwaypointerpoint' entity.", MT_ERROR, MB_OK)
  806.         return
  807.     # Find our master 'Bot Waypointer' (must be only one named like that)
  808.     masters = editor.Root.findallsubitems("Bot Waypointer", ':d')
  809.     if (len(masters) != 1):
  810.         if (len(masters) > 1):
  811.             quarkx.msgbox("A single 'Bot Waypointer' entity could not be determined. Please remove or rename the ones you are not editing for to something else.", MT_ERROR, MB_OK)
  812.         else:
  813.             quarkx.msgbox("Could not find a 'Bot Waypointer' entity. May it have been renamed to something else?", MT_ERROR, MB_OK)
  814.         return
  815.     master = masters[0]
  816.  
  817.     #
  818.     newobj = clipboard[0].copy()
  819.     x, y, z = m.pos.x, m.pos.y, m.pos.z
  820.     mx, my, mz = tuple(master["lastwaypointorigin"].split())
  821.     if   m.view.info["type"] == "XY": z = float(mz)
  822.     elif m.view.info["type"] == "XZ": y = float(my)
  823.     elif m.view.info["type"] == "YZ": x = float(mx)
  824.     newobj["origin"] = str(editor.aligntogrid(quarkx.vect(x, y, z)))
  825.     newobj.translate(quarkx.vect(0, 0, 0)) # damn - if this isn't here, the entity will not show in the views
  826.     newobj["targetname"] = NewWaypointTargetname(master.subitems)
  827.  
  828.     undo = quarkx.action()
  829.     undo.put(master, newobj, None)
  830.     undo.setspec(master, "lastwaypointorigin", newobj["origin"])
  831.     undo.ok(editor.Root, "paste waypoint")
  832.     editor.layout.actionmpp()
  833.  
  834. #
  835. #
  836. #
  837. def backmenu(editor, view=None, origin=None, oldbackmenu=quarkpy.mapmenus.BackgroundMenu):
  838.     # Build the normal context-menu
  839.     menu = oldbackmenu(editor, view, origin)
  840.     # Create a new menu-item, and store the cursor's origin in it.
  841.     menupastebotwaypoint = quarkpy.qmenu.item("&Paste waypoint", PasteBotWaypointClick, "|To paste a bot waypoint, you must first have copied (CTRL+C) a single 'dup botwaypointerpoint' entity into the clipboard.\n\nThis action also creates a new targetname for the waypoint, and keeps it aligned with the previous waypoint selected.")
  842.     menupastebotwaypoint.pos = origin
  843.     menupastebotwaypoint.view = view
  844.     # If there is nothing to paste, disable the menu (note: it could be anything in the clipboard,
  845.     # but we'll test agains that once the user actully performs the action)
  846.     if ((quarkx.pasteobj(0) == 0) or (view is None) or (origin is None)):
  847.         menupastebotwaypoint.state = quarkpy.qmenu.disabled
  848.     # Create a sub-menu that contains the Bot waypointer special menuitems
  849.     popup = quarkpy.qmenu.popup("&Bot waypointer", [menupastebotwaypoint])
  850.     # Put the new sub-menu on top of the context-menu list, and return the lot...
  851.     menu[:0] = [popup]
  852.     return menu
  853.  
  854. quarkpy.mapmenus.BackgroundMenu = backmenu
  855.  
  856. #
  857. #
  858. #
  859. quarkpy.mapduplicator.DupCodes.update({
  860.   "dup botwaypointer":       BotWaypointer,
  861.   "dup botwaypointerpoint":  BotWaypointerPoint,
  862. })
  863.  
  864. # ----------- REVISION HISTORY ------------
  865. # $Log: mapbotwaypointer.py,v $
  866. # Revision 1.3  2003/12/18 21:51:46  peter-b
  867. # Removed reliance on external string library from Python scripts (second try ;-)
  868. #
  869. # Revision 1.2  2002/08/02 19:17:08  decker_dk
  870. # Uhm... Lots of things changed, of which I've forgotten.
  871. #
  872. # Revision 1.1  2002/06/11 17:12:57  decker_dk
  873. # A Bot waypoint-editor. At the moment only works for HPBBot(Half-Life) and LTK/ACEBot(Quake-2)
  874. #
  875.